#include <stdio.h>
#include <wchar.h>
#include <stdlib.h>
#include <string.h>

#ifndef NOWII
#include <malloc.h>
#include <math.h>
#include <ogcsys.h>
#include <gccore.h>
#include <wiiuse/wpad.h>
#include <fat.h>
#include <ogc/isfs.h>
#include <network.h>
#include "http.h"
#endif

#include "list.h"

// 21 Megabytes
#define MAX_FILE_LENGTH 22020096

#ifndef NOWII
// Data structures and libogc stuff
typedef struct _WIIFILE {
	s32 fd;
	u32 pos;
} WIIFILE;

static void *xfb = NULL;
static GXRModeObj *rmode = NULL;
#endif

extern u32 http_status;
extern u32 content_length;


// Function Prototypes

void wait_action();
void wait_home();



// Global variables
#ifdef NOWII
FILE *cdb;
#else
WIIFILE wf;
WIIFILE *cdb;
// On the Wii, the read-buffers for ISFS files MUST be
// aligned char arrays
char readbuffer[1024] ATTRIBUTE_ALIGN(32);
#endif
unsigned int lastMessageId = 0;
FILE *fout;

node recent_title_ids;
//char recent_title_ids[5][6];  // An array of the 5 most recent title_ids
//int recent_title_index = 0;   // An index for the previous array


// Byte Swappers !!
static inline unsigned short bswap_16(unsigned short x) {
  return (x>>8) | (x<<8);
}

static inline unsigned int bswap_32(unsigned int x) {
  return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16));
}

static inline unsigned long long bswap_64(unsigned long long x) {
  return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32));
}


// Architecture-dependant file functions
size_t read(void* buf, size_t size, size_t count, void *fptr)
{
#ifdef NOWII
	return fread(buf, size, count, (FILE *)fptr);
#else
	// We need a WIIFILE pointer
	WIIFILE *wfptr = fptr;
	ISFS_Read(wfptr->fd, readbuffer, size*count);
	memcpy(buf, readbuffer, size*count);
	// Update the position cursor
	wfptr->pos += size*count;
	return size*count;
#endif
}

int seek(void *fptr, int offset, int origin)
{
#ifdef NOWII
	return fseek((FILE*)fptr, offset, origin);
#else
	// We need a WIIFILE pointer
	WIIFILE *wfptr = fptr;
	ISFS_Seek(wfptr->fd, offset, origin);
	// Update the WIIFILE pos
	if (origin == SEEK_SET)
	{
		wfptr->pos = offset;
	}
	else if (origin == SEEK_CUR)
	{
		wfptr->pos += offset;
	}
	return 0;
#endif
}



// Flow functions

void parseHeaders()
{
	char vff[4];

	//printf("so uint %ld\n", sizeof(unsigned int));

	read(vff, 1, 4, cdb);
	
	// "VFF "
	if (memcmp(vff, "VFF ", 4) != 0)
	{
		fprintf(stderr, "WARNING: cdb file format not detected!\n");
	}
	else
	{
		printf("cdb file format detected!\n");
	}

	// Last message id
	seek(cdb, 0x29020, SEEK_SET);
	read(&lastMessageId, 4, 1, cdb);

#ifdef X86
	lastMessageId = bswap_32(lastMessageId);
#endif

	printf("Last Message Id: 0x%x (%d)\n", lastMessageId, lastMessageId);

	seek(cdb, 0x2a020, SEEK_SET);

	//wait_action();
}

void add_title(char *tid, char *code)
{
	char buf[6];
	//node *aux = (node *)recent_title_ids.next;
	
	memcpy(buf, tid, 4);
	buf[4] = code[0];
	buf[5] = code[1];
	
	//printf("Adding title_id: %s\n", buf);
	
	delete_tid((node *)&recent_title_ids, buf);
	
	// Put the title_id and code in the array
	add_node((node *)&recent_title_ids, buf);
}

void processLogrecord()
{
	unsigned short title[21];
	char title_string[21];
	unsigned short i = 0;
	char title_id[4];
    char title_id_code[2];

	// Skip 4 bytes
	seek(cdb, 4, SEEK_CUR);

	// Read game title
	read(title, 2, 21, cdb);
	//printf("Channel Title: ");
	memset(title_string, '\0', 21);
	for ( i = 0 ; i < 21 ; i++ )
	{
#ifdef X86
		title[i] = bswap_16(title[i]);
#endif
		//printf("0x%x ", message[i]);
		//printf("%c", title[i]);
		title_string[i] = (char)title[i];
	}
	//printf("\n");

	// Skip extended title (21 16-bit chars)
	seek(cdb, 42, SEEK_CUR);

	// Skip start time
	seek(cdb, 8, SEEK_CUR);

	// Skip end time
	seek(cdb, 8, SEEK_CUR);

	// Read title_id
	read(title_id, 1, 4, cdb);
	//printf("Title ID: %s\n", title_id);

	// Read 2 bytes code
	//seek(cdb, 2, SEEK_CUR);
    read(title_id_code, 1, 2, cdb);
    //printf("Title ID CODE: %c%c\n", title_id_code[0], title_id_code[1]);
    
    // Change title_id_code 00 to string "00" in order to be printable
    if ( (title_id_code[0] == 0) && (title_id_code[1] == 0) )
    {
        title_id_code[0] = '0';
        title_id_code[1] = '0';
    }

	// Skip 18 bytes
	seek(cdb, 18, SEEK_CUR);
	
	// Skip 8 bytes (others)
	seek(cdb, 8, SEEK_CUR);

#ifdef OUTFILE
    if ( isalnum(title_string[0]) && isalnum(title_id[0]))
    {
	    fprintf(fout, "\t<channel title=\"%s\" title_id=\"%c%c%c%c\" code=\"%c%c\"/>\n", 
		    title_string, 
		    title_id[0], 
		    title_id[1],
		    title_id[2],
		    title_id[3],
		    title_id_code[0],
		    title_id_code[1]);
	}
#endif
	
	add_title(title_id, title_id_code);
}

unsigned int processCDBFILE()
{
	char cdbfile[8];
	char temp[12];	
	short int isPlaytimelog = 0;
	unsigned int mid;
	unsigned int channels;
	unsigned int timestamp;
	char ri_5[4];
	unsigned int start;
	unsigned int end;
	int messageLength;
	unsigned short message[1024];
	//char message[1024];
	int i = 0;
	unsigned char code[4];

	//printf("%ld\n", sizeof(wchar_t));

	memset(cdbfile, 0, 8);
	read(cdbfile, 1, 8, cdb);
	if (memcmp(cdbfile, "CDBFILE", 7))
	{
		fprintf(stderr, "WARNING: CDBFILE constant not found! %s\n", cdbfile);
		exit(-1);
	}
	else
	{
		//printf("CDBFILE constant found\n");
	}

	// Skip 12 bytes
	seek(cdb, 12, SEEK_CUR);

	// Read "playtimelog" (or "ripl_board_")
	read(temp, 1, 11, cdb);
	if (!memcmp(temp, "playtimelog", 11)==0)
	{
		//printf("Email message\n");
		isPlaytimelog = 0;
	}
	else
	{
		//printf("Log entry\n");
		isPlaytimelog = 1;
	}
	
	// Skip some bytes
	seek(cdb, 81, SEEK_CUR);

	// Read message id
	read(&mid, 4, 1, cdb);
#ifdef X86
	mid = bswap_32(mid);
#endif
	//printf("Message ID: 0x%x (%d)\n", mid, mid);


	// Read number of channels (entries) in this message
	read(&channels, 4, 1, cdb);
#ifdef X86
	channels = bswap_32(channels);
#endif
	//printf("Channels: 0x%x (%d)\n", channels, channels);

	if (channels > 9)
	{
		channels = 9;
		//fprintf(stderr, "WARNING: Fixing channels to 9\n");
	}

	// Skip 4 bytes
	seek(cdb, 4, SEEK_CUR);

	// Read timestamp
	read(&timestamp, 4, 1, cdb);	
#ifdef X86
	timestamp = bswap_32(timestamp);
#endif
	//printf("Timestamp: 0x%x (%d) seconds since 2k\n", timestamp, timestamp);


#ifdef OUTFILE
	if (isPlaytimelog)
	{
		fprintf(fout, "<cdbfile id=\"%d\" channels=\"%d\" timestamp=\"%d\">\n", mid, channels, timestamp);
	}
#endif

	// Skip 896 bytes of padding
	seek(cdb, 896, SEEK_CUR);

	// Read "RI_5" constant
	read(ri_5 , 1, 4, cdb);
	if (!memcmp(ri_5, "RI_5", 4)==0)
	{
		fprintf(stderr, "WARNING: RI_5 constant not found!\n");
		exit(-1);
	}
	else
	{
		//printf("RI_5 constant found\n");
	}

	// Skip 12 bytes of padding
	seek(cdb, 12, SEEK_CUR);
	
	// Skip ticks timestamp (u64)
	seek(cdb, 8, SEEK_CUR);

	// Skip 8 bytes of padding
	seek(cdb, 8, SEEK_CUR);

	// Skip 240 zeroes
	seek(cdb, 240, SEEK_CUR);

	// Skip 12 bytes of padding
	seek(cdb, 12, SEEK_CUR);

	// Read length of data between "RI_5" and start of message
	// Should be 0x148
	read(&start, 4, 1, cdb);
#ifdef X86
        start = bswap_32(start);
#endif
        //printf("Start offset: 0x%x (%d)\n", start, start);

	// Skip 12 bytes of padding
	seek(cdb, 12, SEEK_CUR);
	
	// Read length of data between "RI_5" and end of message
	read(&end, 4, 1, cdb);
#ifdef X86
        end = bswap_32(end);
#endif
        //printf("End offset: 0x%x (%d)\n", end, end);

	messageLength = end - start;
	//printf("Expected message length %d bytes\n", messageLength);

	// Skip 0x18 bytes of padding
	seek(cdb, 0x18, SEEK_CUR);

	if (!isPlaytimelog)
	{
		// Stop here if it's an email
		return mid;
	}	

	// Read message
	if (messageLength > 0)
	{
		memset(message, 0, 2048);
		read(message, 2, messageLength/2, cdb);
		//printf("Message:\n");
		for ( i = 0 ; i < 1024 ; i++ )
		{
#ifdef X86
			message[i] = bswap_16(message[i]);
#endif
			//printf("0x%x ", message[i]);
			//printf("%c", message[i]);
		}
		//printf("\n");
	
		// Skip the rest of the message if it's too long
		if (messageLength > 2048)
		{
			seek(cdb, messageLength - 2048, SEEK_CUR);
		}
	}


	if (isPlaytimelog)
	{
		// Read a 4 byte code
		read(code, 1, 4, cdb);
		//printf("Code: 0x%x 0x%x 0x%x 0x%x\n", code[0], code[1], code[2], code[3]);	
		
		// Check which type of code is it
		if ( (code[0] == 0x00) || (code[0] == 0x01) )
		{
			// Skip 4 bytes
			seek(cdb, 4, SEEK_CUR);
		}
		else
		{
			// Skip 12 bytes
			seek(cdb, 12, SEEK_CUR);
		}

		for ( i = 1 ; i <= channels ; i++ )
		{
			processLogrecord();
		}	
	}

#ifdef OUTFILE
	fprintf(fout, "</cdbfile>\n");
#endif

	// Return this message id
	return mid;
}

void syncCDBFILE()
{
	char cdbfile[7];
	long int position;
	
	// Calculate the file position where to start looking
	// It must be aligned with 0x100 + 0x20 , so we take the
	// current address, and look for the PREVIOUS memory address
	// aligned with that
#ifdef NOWII
	position=(( (int) ( (ftell(cdb)-0x21)/0x100) )+1)*0x100 + 0x20;
#else
	position=(( (int) ( (cdb->pos-0x21)/0x100) )+1)*0x100 + 0x20;
#endif

	seek(cdb, position, SEEK_SET);
	read(cdbfile, 1, 7, cdb);
	while ( memcmp(cdbfile, "CDBFILE", 7) )
	{
		position += 0x100;  // Advance 0x100 bytes
		//seek(cdb, -6, SEEK_CUR);
		seek(cdb, position, SEEK_SET);
		read(cdbfile, 1, 7, cdb);
	}
	//seek(cdb, -7, SEEK_CUR);
	seek(cdb, position, SEEK_SET);
}

#ifndef NOWII
void libogc_init()
{
// Initialise the video system
	VIDEO_Init();
	
	// This function initialises the attached controllers
	WPAD_Init();
	
	// Obtain the preferred video mode from the system
	// This will correspond to the settings in the Wii menu
	rmode = VIDEO_GetPreferredMode(NULL);

	// Allocate memory for the display in the uncached region
	xfb = MEM_K0_TO_K1(SYS_AllocateFramebuffer(rmode));
	
	// Initialise the console, required for printf
	console_init(xfb,20,20,rmode->fbWidth,rmode->xfbHeight,rmode->fbWidth*VI_DISPLAY_PIX_SZ);
	
	// Set up the video registers with the chosen mode
	VIDEO_Configure(rmode);
	
	// Tell the video hardware where our display memory is
	VIDEO_SetNextFramebuffer(xfb);
	
	// Make the display visible
	VIDEO_SetBlack(FALSE);

	// Flush the video register changes to the hardware
	VIDEO_Flush();

	// Wait for Video setup to complete
	VIDEO_WaitVSync();
	if(rmode->viTVMode&VI_NON_INTERLACE) VIDEO_WaitVSync();


	// The console understands VT terminal escape codes
	// This positions the cursor on row 2, column 0
	// we can use variables for this with format codes too
	// e.g. printf ("\x1b[%d;%dH", row, column );
	printf("\x1b[2;0H");
	
	printf("Hello Wii!\n");
}
#endif


void wait_home()
{
#ifndef NOWII
	while(1) {

		// Call WPAD_ScanPads each loop, this reads the latest controller states
		WPAD_ScanPads();

		// WPAD_ButtonsDown tells us which buttons were pressed in this loop
		// this is a "one shot" state which will not fire again until the button has been released
		u32 pressed = WPAD_ButtonsDown(0);

		// We return to the launcher application via exit
		if ( pressed & WPAD_BUTTON_HOME ) exit(0);

		// Wait for the next frame
		VIDEO_WaitVSync();
	}
#endif
}

void wait_action()
{
#ifndef NOWII
	while(1) {
		WPAD_ScanPads();
		u32 pressed = WPAD_ButtonsDown(0);
		if ( pressed & WPAD_BUTTON_A ) return;
		if ( pressed & WPAD_BUTTON_HOME ) exit(0);
		//VIDEO_WaitVSync();
	}
#endif
}


void show_recent_titles()
{
	char buf[7];
	node *aux = recent_title_ids.next;

	printf("The most recent title_ids:\n");	
	while (aux != NULL)
	{
		memcpy(buf, aux->tid, 6);
		buf[6] = '\0';
		printf("%s ", buf);
		aux = aux->next;
	}
	printf("\n");
}

void build_http_query(char *query)
{
	char buf[7];
	node *aux = recent_title_ids.next;
	int count = 0;
	
	memset(query, '\0', 1024);
	strcpy(query, "http://gamercardwii.appspot.com/?friend_code=W1&password=secret");
	
	//printf("The most recent title_ids:\n");	
	while ( (aux != NULL) && (count < 5) )
	{
		memset(buf, '\0', 7);
		memcpy(buf, aux->tid, 6);
		//printf("%s ", buf);
		
		strcat(query,  "&title_id=");
		strcat(query, buf);
		count++;
		
		aux = aux->next;
	}
}

void http_connection()
{
	int i = 0;
	s32 ret;
	char localip[16] = {0};
	char gateway[16] = {0};
	char netmask[16] = {0};	
	//u32 status;
	char query[1024];
	
	printf("Configuring network ...\n");

	// Configure the network interface
	ret = if_config ( localip, netmask, gateway, TRUE);
	if (ret>=0) 
	{
		printf ("network configured, ip: %s, gw: %s, mask %s\n", localip, gateway, netmask);
	}
	else
	{
		printf ("network configuration failed!\n");
	}
	
	printf("HTTP Connection...");
	/*
	struct block hblock = downloadfile("http://www.google.es/images/srpr/nav_logo13.png");
	if (hblock.size)
	*/
	
	// Build the http query
	build_http_query(query);
	
	for ( i = 0 ; i < 1 ; i++ )
	{
		//bool res = http_request ("http://gamercardwii.appspot.com/?friend_code=W1password=secret&title_id=RD2X41", 30240);
		//bool res = http_request ("http://gamercardwii.appspot.com/?title_id=FAPG", 1024*1024);
		//printf("HTTP QUERY: %s\n", query);
		bool res = http_request (query, 30240);
		if (res)
		{
			//printf("%c%c%c%c\n", hblock.data[0],hblock.data[1],hblock.data[2],hblock.data[3]);
			printf("The connection is made!\n");
		}
		else
		{
			printf("No data\n");
		}
		printf("http_status: %d %d\n", http_status, content_length);
	}
}

int main(int argc, char **argv)
{
	unsigned int currentMessageId = 0;
	//int errcode;

#ifndef NOWII
	// Filestats must be static and 32-aligned
	//static fstats stats ATTRIBUTE_ALIGN(32);
	//u8 filearray[MAX_FILE_LENGTH] ATTRIBUTE_ALIGN(32);
	
#endif

	init_node(&recent_title_ids);
	
#ifndef NOWII
	libogc_init();
	printf("Waiting for network to initialize...");
	s32 ip;
	while ((ip = net_init()) == -EAGAIN)
	{
		printf(".");
		usleep(100 * 1000); //100ms
	}
	printf("\n");
#endif

	//http_connection();
	
#ifndef NOWII
	//wait_home();
#endif
	
	printf("Opening file . . .\n");
	cdb = NULL;

#ifdef NOWII
	cdb= fopen("cdb.vff", "r");	
#else
	ISFS_Initialize();
	cdb = &wf;
	cdb->fd = ISFS_Open("/title/00000001/00000002/data/cdb.vff", ISFS_OPEN_READ);
	cdb->pos = 0;
#endif	
	
	if (!cdb)
	{
		fprintf(stderr, "ERROR: Could not open cdb file!\n");
		exit(-1);
	}
	else
	{
		printf("Succesfully opened cdb.vff\n");
	}

#ifdef OUTFILE
	fout = fopen("cdb.xml", "w");
	if (!fout)
	{
		fprintf(stderr, "ERROR: Could not open output file!\n");
		exit(-1);
	}
	fprintf(fout, "<cdb>\n");
#endif

/*
#ifndef NOWII
	printf("Trying to read the entire file...\n");
	errcode = ISFS_GetFileStats(cdb->fd, &stats);
	if (errcode < 0)
	{
		printf("Error reading file stats: %d\n", errcode);
	}
	else
	{
		printf("The file length is %u\n", stats.file_length);
		errcode = ISFS_Read(cdb->fd, filearray, stats.file_length);
		if (errcode <0)
		{
			printf("Error reading the whole file: %d\n", errcode);
		}
		else
		{
			printf("Read the whole file to filearray\n");
		}
	}
#endif
*/

/*
#ifndef NOWII
	ISFS_Close(cdb->fd);
	ISFS_Deinitialize();
#endif
	exit(-1);
*/

	printf("Parsing headers...\n");

	parseHeaders();

	printf("Headers parsed\n");
	printf("Processing messages...\n");
	
	/*
	processCDBFILE();
	syncCDBFILE();
	processCDBFILE();
	syncCDBFILE();
	processCDBFILE();
	*/

	currentMessageId =  processCDBFILE();

	//wait_action();

	while (currentMessageId != lastMessageId)
	{
		syncCDBFILE();
		currentMessageId = processCDBFILE();
		//printf("\n############\n");
		//printf("Message ID 0x%x PROCESSED\n", currentMessageId);
		//printf("\n############\n");
	}

#ifdef NOWII	
	fclose(cdb);
#else
	ISFS_Close(cdb->fd);
	cdb->pos=-1;
	ISFS_Deinitialize();
#endif
	
#ifdef OUTFILE
	fprintf(fout, "\n</cdb>");
	fclose(fout);
#endif

	printf("cdb file processed!\n");
	
	show_recent_titles();
	
	http_connection();
	
	printf("You can press HOME now\n");
	
#ifndef NOWII
	wait_home();
#endif

	exit(0);
	return 0;	
}
